﻿using Clancy.Communication.Interfaces;
using Clancy.Data;
using Clancy.Interfaces;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading.Tasks;

namespace Clancy.Service.Base
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    internal class LiveStreamFeedbackService : CommunicationServiceBase, ILiveStreamInterface
    {
        private readonly Dictionary<Guid, List<ILiveStreamInterfaceCallbackService>> m_ClientSubscriptions =
            new Dictionary<Guid, List<ILiveStreamInterfaceCallbackService>>();

        private readonly List<ILiveStreamInterfaceCallbackService> m_RecordingSubscriptions =
            new List<ILiveStreamInterfaceCallbackService>();

        private readonly Dictionary<Guid, IDisposable> m_SubscribedSurveilances = new Dictionary<Guid, IDisposable>();
        private readonly ISurveilanceLogic m_SurveilanceLogic;

        public LiveStreamFeedbackService(ISurveilanceLogic surveilanceLogic, ISurveilanceStorage surveilanceStorage, IRecordingManager recordingManager)
        {
            m_SurveilanceLogic = surveilanceLogic;
            ISurveilanceStorage surveilanceStorage1 = surveilanceStorage;

            AttachToCollection(surveilanceStorage1.SurveilanceObjects, SubscribeSurveilances, UnSubscribeFromSurveilanceResults);
            AttachToCollection(recordingManager.RecordingCollection, SendRecordingAdded, SendRecordingRemoved);

            SubscribeSurveilances(surveilanceStorage1.SurveilanceObjects);
        }

        public void Close(Guid surveilanceId)
        {
            ILiveStreamInterfaceCallbackService currentChannel = OperationContext.Current.GetCallbackChannel<ILiveStreamInterfaceCallbackService>();
            lock (m_ClientSubscriptions)
            {
                if (!m_ClientSubscriptions.ContainsKey(surveilanceId))
                {
                    return;
                }

                m_ClientSubscriptions[surveilanceId].Remove(currentChannel);
            }
        }

        public void CloseAll()
        {
            ILiveStreamInterfaceCallbackService currentChannel = OperationContext.Current.GetCallbackChannel<ILiveStreamInterfaceCallbackService>();

            lock (m_ClientSubscriptions)
            {
                foreach (List<ILiveStreamInterfaceCallbackService> subscriptionList in m_ClientSubscriptions.Values.Where(subscriptionList => subscriptionList.Contains(currentChannel)))
                {
                    subscriptionList.Remove(currentChannel);
                }
            }
        }

        public override void Dispose()
        {
            Guid[] allSurveilanceIds = m_SubscribedSurveilances.Keys.ToArray();
            foreach (Guid id in allSurveilanceIds)
            {
                UnSubscribeFromSurveilanceResult(id);
            }
            base.Dispose();
        }

        public bool Open(Guid surveilanceId)
        {
            ILiveStreamInterfaceCallbackService currentChannel = OperationContext.Current.GetCallbackChannel<ILiveStreamInterfaceCallbackService>();
            lock (m_ClientSubscriptions)
            {
                if (!m_ClientSubscriptions.ContainsKey(surveilanceId))
                {
                    m_ClientSubscriptions[surveilanceId] = new List<ILiveStreamInterfaceCallbackService>();
                }

                if (m_ClientSubscriptions[surveilanceId].Contains(currentChannel))
                    return false;

                if (!m_ClientSubscriptions[surveilanceId].Contains(currentChannel))
                {
                    m_ClientSubscriptions[surveilanceId].Add(currentChannel);
                    return true;
                }
                return false;
            }
        }

        public bool SubsribeForRecordings()
        {
            ILiveStreamInterfaceCallbackService currentChannel = OperationContext.Current.GetCallbackChannel<ILiveStreamInterfaceCallbackService>();

            lock (m_RecordingSubscriptions)
            {
                if (m_RecordingSubscriptions.Contains(currentChannel))
                    return false;

                m_RecordingSubscriptions.Add(currentChannel);
                return true;
            }
        }

        public bool UnsubsribeForRecordings()
        {
            ILiveStreamInterfaceCallbackService currentChannel = OperationContext.Current.GetCallbackChannel<ILiveStreamInterfaceCallbackService>();

            lock (m_RecordingSubscriptions)
            {
                if (!m_RecordingSubscriptions.Contains(currentChannel))
                    return false;

                m_RecordingSubscriptions.Remove(currentChannel);
                return true;
            }
        }

        private void BroadcastResultFrame(SurveilanceObject surveilanceObject, ISurveilanceResultFrame frame)
        {
            List<ILiveStreamInterfaceCallbackService> broadcastChannels;
            lock (m_ClientSubscriptions)
            {
                if (!m_ClientSubscriptions.ContainsKey(surveilanceObject.Id))
                    return;

                broadcastChannels = m_ClientSubscriptions[surveilanceObject.Id];
            }

            List<ILiveStreamInterfaceCallbackService> failedChannels = new List<ILiveStreamInterfaceCallbackService>();
            Parallel.ForEach(broadcastChannels,
                channel =>
                {
                    try
                    {
                        channel.ReturnSurveilanceResultFrame(surveilanceObject, frame as SurveilanceResultFrame);
                    }
                    catch
                    {
                        failedChannels.Add(channel);
                    }
                });

            ClearFailedChannels(surveilanceObject, failedChannels);
        }

        private void ClearFailedChannels(SurveilanceObject surveilanceObject, IEnumerable<ILiveStreamInterfaceCallbackService> failedChannels)
        {
            lock (m_ClientSubscriptions)
            {
                foreach (ILiveStreamInterfaceCallbackService channel in failedChannels)
                {
                    m_ClientSubscriptions[surveilanceObject.Id].Remove(channel);
                }
            }
        }

        private void SendRecordingAdded(IList items)
        {
            ILiveStreamInterfaceCallbackService[] receivers;
            lock (m_RecordingSubscriptions)
            {
                receivers = m_RecordingSubscriptions.ToArray();
            }

            foreach (ILiveStreamInterfaceCallbackService receiver in receivers)
            {
                receiver.ReturnRecoridngsAdded(items.Cast<RecordingItem>().ToArray());
            }
        }

        private void SendRecordingRemoved(IList items)
        {
            ILiveStreamInterfaceCallbackService[] receivers;
            lock (m_RecordingSubscriptions)
            {
                receivers = m_RecordingSubscriptions.ToArray();
            }

            foreach (ILiveStreamInterfaceCallbackService receiver in receivers)
            {
                receiver.ReturnRecoridngsRemoved(items.Cast<RecordingItem>().ToArray());
            }
        }

        private void SubscribeSurveilances(IList item)
        {
            SurveilanceObject[] newSurveilanceObjects = item.Cast<SurveilanceObject>().ToArray();
            foreach (SurveilanceObject surveilanceObject in newSurveilanceObjects)
            {
                SubscribeToSurveilanceResults(surveilanceObject);
            }
        }

        private void SubscribeToSurveilanceResults(ISurveilanceObject surveilanceObject)
        {
            lock (m_ClientSubscriptions)
            {
                if (m_ClientSubscriptions.ContainsKey(surveilanceObject.Id))
                    return;
            }

            ISurveilanceStatus surveilanceStatus = m_SurveilanceLogic.GetSurveilance(surveilanceObject);
            lock (m_SubscribedSurveilances)
            {
                m_SubscribedSurveilances[surveilanceObject.Id] = surveilanceStatus.CurrentProcessedFrame.Subscribe(
                    frame => BroadcastResultFrame(surveilanceObject as SurveilanceObject, frame));
            }
        }

        private void UnSubscribeFromSurveilanceResult(Guid surveilanceObjectId)
        {
            lock (m_SubscribedSurveilances)
            {
                if (!m_SubscribedSurveilances.ContainsKey(surveilanceObjectId))
                    return;

                m_SubscribedSurveilances[surveilanceObjectId].Dispose();
                m_SubscribedSurveilances.Remove(surveilanceObjectId);
            }
        }

        private void UnSubscribeFromSurveilanceResults(IList item)
        {
            SurveilanceObject[] newSurveilanceObjects = item.Cast<SurveilanceObject>().ToArray();
            foreach (var surveilanceObject in newSurveilanceObjects)
            {
                UnSubscribeFromSurveilanceResult(surveilanceObject.Id);
            }
        }
    }
}